import { degreeInfo, KEYBOARD_KEY, ParameterObjectType } from '@amcax/base';
import { useEffect, useMemo, useRef, useState } from 'react';
import cx from 'classnames';
import { DimColors } from '../types';
import { sketchEventCenter } from '@app-base/environments/nextcad/events/SketchEventCenter';
import { useFormatInput } from '@app-base/common/components/command-form/useFormatInput';
import useStateRef from 'react-usestateref';
import { CSS2DInputWrapper } from '@app-base/common/components/css-2d-react-object/InputWrapper';
import { InputWithFormat } from '@app-base/common/components/command-form/InputWithFormat';
import { useNumberWithUnit } from '@app-base/common/hooks/unit';
import { isEmpty } from 'lodash';
import { useMemoizedFn } from 'ahooks';
import { wsApi } from '@app-base/server';

export type DimLabelProps = {
  type: ParameterObjectType;
  disabled: boolean;
  prefix: string;
  value: number;
  param: string;
  allowExpression: boolean;
  isEdit: boolean;
  allowActive: boolean;
  hovered: boolean;
  selected: boolean;
  onChange: (active: boolean, text?: number, param?: string) => void;
  multiple: number;
};

const status = [
  'default',
  'dimHorizontal',
  'dimVertical',
  'dimlinear',
  'dimAngle',
  'dimDiameter',
  'dimRadius',
  'dimIntegration',
];

const VIEW_PRECISION = 3;
const INPUT_PRECISION = 3;

export const DimLabel = ({
  type,
  value,
  prefix,
  isEdit,
  onChange,
  disabled,
  allowActive,
  hovered,
  selected,
  multiple,
  param,
  allowExpression = false,
}: DimLabelProps) => {
  const { hasUnit, currentUnit } = useFormatInput({
    inputType: type,
    outerValue: value,
  });

  const hasChanged = useRef(true);

  useEffect(() => {
    if (isEdit) {
      hasChanged.current = false;
      // isEdit设置完成，input宽度变化后再设置
      setTimeout(() => {
        setWidth(inputDomRef.current.scrollWidth);
      });
    } else {
      setWidth(0);
    }
  }, [isEdit]);

  const { getNumWithPrecision } = useNumberWithUnit();

  const getInnerValue = () => {
    const precision = isEdit ? INPUT_PRECISION : VIEW_PRECISION;
    if (!isEmpty(param) && isEdit) {
      return param;
    }
    return getNumWithPrecision(value * multiple, precision);
  };

  const [innerValue, setInnerValue] = useStateRef(getInnerValue());

  const [, setIsDialogOpen, isDialogOpenRef] = useStateRef(false);

  useEffect(() => {
    setInnerValue(getInnerValue());
    if (isEdit) {
      window.requestAnimationFrame(() => {
        inputDomRef.current?.focus({ preventScroll: true });
        inputDomRef.current?.select();
      });
    }
  }, [value]);

  const [width, setWidth] = useState(null);

  const onSubmit = useMemoizedFn(async (newVal?: string | number) => {
    if (!hasChanged.current) {
      onChange(false);
      return;
    }
    let value: number;
    let param: string;

    const valueToUpdate = newVal ?? innerValue;

    changeEventEmitState.current = 'emitting';
    if (typeof valueToUpdate === 'string') {
      value = await wsApi.getFormulaValue(valueToUpdate);
      param = valueToUpdate;
    } else {
      value = valueToUpdate / multiple;
    }
    onChange(false, value, param);
    changeEventEmitState.current = 'emitted';
  });

  const changeEventEmitState = useRef<'idle' | 'emitting' | 'emitted'>(
    'emitted',
  );

  useEffect(() => {
    if (!isEdit) {
      if (changeEventEmitState.current !== 'idle') {
        return;
      }
      onSubmit();
    } else {
      setInnerValue(getInnerValue());
      setTimeout(() => {
        inputDomRef.current?.focus({ preventScroll: true });
        inputDomRef.current?.select();
      });

      changeEventEmitState.current = 'idle';
    }
  }, [isEdit]);

  const onKeyUp = (e: KeyboardEvent) => {
    if (e.key === KEYBOARD_KEY.ESC) {
      changeEventEmitState.current = 'idle';
      onChange(false);
      return;
    }
    if (e.key === KEYBOARD_KEY.ENTER && !isDialogOpenRef.current) {
      onSubmit();
      return;
    }
  };

  useEffect(() => {
    if (isEdit) {
      document.addEventListener('keyup', onKeyUp, { capture: true });
    }

    return () => {
      document.removeEventListener('keyup', onKeyUp, { capture: true });
    };
  }, [isEdit]);

  const colors = useMemo(() => {
    if (disabled) {
      return DimColors.disable;
    }
    if (selected) {
      return DimColors.select;
    }

    if (hovered) {
      return DimColors.hover;
    }

    return DimColors.normal;
  }, [disabled, hovered, selected]);

  const baseDomRef = useRef<HTMLDivElement>();
  const inputContainerRef = useRef<HTMLDivElement>();
  const inputDomRef = useRef<HTMLInputElement>();

  const onClickOut = useMemoizedFn((e: MouseEvent) => {
    // 未在编辑状态，不触发
    if (!isEdit) {
      return;
    }

    // 此值不可setTimeout后计算
    const isClickOut =
      inputDomRef.current != null &&
      !inputContainerRef.current?.contains(e.target as Node) &&
      !objectTreeDom?.contains(e.target as Node) &&
      !isDialogOpenRef.current;

    // setTimeout用于保证下方代码比输入框的onBlur更晚触发
    // isEdit的检测置于外部，原因见事件监听部分
    setTimeout(() => {
      if (changeEventEmitState.current !== 'idle') {
        return;
      }
      if (isClickOut) {
        onSubmit();
      } else if (disabled) {
        onChange(false);
      }
    });
  });

  const objectTreeDom = useMemo(
    () => document.getElementsByClassName('ant-tree-list')[0],
    [],
  );

  useEffect(() => {
    // 外部将isEdit置为true的鼠标事件会同时触发该监听
    // 需要将capture设为true，保证其更早触发，不然会在isEdit被设为true后立刻触发clickOut，又将isEdit设为false
    document.addEventListener('click', onClickOut, { capture: true });

    return () => {
      document.removeEventListener('click', onClickOut, { capture: true });
    };
  }, []);

  return (
    <div
      className='relative'
      onPointerUp={(e) => {
        if (!disabled && e.button === 0 && allowActive && !isEdit) {
          e.stopPropagation();
          e.preventDefault();
        }
      }}
    >
      {
        <div
          ref={baseDomRef}
          className={cx(isEdit && 'hidden', 'cursor-pointer rounded-sm')}
          style={{
            backgroundColor: colors?.mask,
            color: colors?.text,
          }}
          onClick={(e) => {
            if (
              !sketchEventCenter().dimIsMove &&
              status.includes(sketchEventCenter().state) &&
              allowActive &&
              !disabled &&
              e.button === 0
            ) {
              onChange(true);
            }
          }}
        >
          {prefix +
            getInnerValue() +
            (hasUnit && currentUnit.name === degreeInfo.name ? '°' : '')}
        </div>
      }
      {isEdit && (
        <CSS2DInputWrapper>
          <div ref={inputContainerRef}>
            <InputWithFormat
              ref={inputDomRef}
              className='text-[15px] absolute translate-x-[-50%] translate-y-[-50%]'
              style={{
                width: Math.max(width, 30) + 20,
                height: 32,
              }}
              value={innerValue}
              onChange={(newValue) => {
                hasChanged.current = true;
                if (newValue != null) {
                  setInnerValue(newValue);
                  onSubmit(newValue);
                } else {
                  setInnerValue(value * multiple);
                }
              }}
              inputType={type}
              onDialogVisibleChange={setIsDialogOpen}
              disableParameter={!allowExpression}
            />
          </div>
        </CSS2DInputWrapper>
      )}
    </div>
  );
};
